#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "CollView.h"

#include "Class.h"
#include "String.h"
#include "SeqColl.h"
#include "VObject.h"
#include "CmdNo.h"
#include "Error.h"
#include "Math.h"
#include "WindowSystem.h"

extern bool testopen;

//---- CollectionView ----------------------------------------------------------

NewMetaImpl(CollectionView,View, (TP(coll), T(selection), T(gap), T(minExtent),
					T(rows), T(cols), TP(defaultItem)));

CollectionView::CollectionView(EvtHandler *eh, SeqCollection *m, CollViewOptions o,
						int r, int c) : View(eh, gRect0)
{
	SetFlag(o);
	defaultItem= new VObject;
	rows= r;
	cols= c;
	SetCollection(m);
	SetFlag(eVObjKbdFocus);
}

CollectionView::~CollectionView()
{
	if (coll) {
		coll->RemoveObserver(this);
		coll->FreeAll();
		SafeDelete(coll);
	}
	SafeDelete(xPos);
	SafeDelete(yPos);
	SafeDelete(defaultItem);
}

//---- init methods

void CollectionView::SetSelection(const Rectangle &newsel, bool sendcontrol)
{
	if (selection != newsel) {
		if (IsOpen()) {
			InvalidateRect(ItemRect(selection));
			InvalidateRect(ItemRect(newsel));
		}
		selection= newsel;
	}
	if (sendcontrol)
		DoSelect(selection, WindowSystem::Clicks);
}

void CollectionView::SetColsRows(const Point &cr)
{
	cols= cr.x;
	rows= cr.y;
	Modified();
}

void CollectionView::DoSelect(Rectangle r, int clicks)
{
	if (r.IsNotEmpty()) {
		int partcode= clicks >= 2 ? cPartCollDoubleSelect : cPartCollSelect;
		Control(GetId(), partcode, (void*) r.origin.y);
	}
}

void CollectionView::ClearSelection(bool)
{
	SetSelection(gRect0);
}

void CollectionView::SetCollection(SeqCollection *m, bool freeold)
{
	if (coll) {
		coll->RemoveObserver(this);
		/*
		{
			coll->ForEach(VObject,Open)(FALSE);
		}
		*/
		{
			coll->ForEach(VObject,ClearContainer)(this);
		}
		if (freeold) {
			coll->FreeAll();
			delete coll;
		}
	}
	coll= m;
	if (coll) {
		coll->assertclass(Meta(VObject));
		{
			coll->ForEach(VObject,SetContainer)(this);
		}
		if (IsOpen()) {
			coll->ForEach(VObject,Open)(TRUE);
		}
		coll->AddObserver(this);
	}
	selection= gRect0;
	Modified();
	//if (IsOpen())
	//    Scroll(cPartScrollAbs, gPoint0, FALSE);
}

void CollectionView::SetDefaultItem(class VObject *d)
{
	if (defaultItem)
		delete defaultItem;
	defaultItem= d;
}

void CollectionView::SetMinExtent(Point e)
{
	if (minExtent != e) {
		minExtent= e;
		Modified();
	}
}

Metric CollectionView::GetMinSize()
{
	Update();
	return GetExtent();
}

void CollectionView::Open(bool m)
{
	View::Open(m);
	if (testopen) {
		if (m) {
			if (coll) {
				coll->ForEach(VObject,Open)(TRUE);
			}
		} else {
			if (!IsOpen() && coll) {
				coll->ForEach(VObject,Open)(FALSE);
			}
		}
	}
}

//---- layout

void CollectionView::ConstrainScroll(Point *p)
{
	if (xPos) {
		bool outside;
		Point pp(PointToItem(*p, &outside));
		if (! outside)
			p->y= ItemRect(pp).origin.y;
	}
}

void CollectionView::CalcScrollStep(Point &delta, const Point &relOrigin,
							const Point &scroll)
{
	bool outside;
	Point pp(PointToItem(relOrigin, &outside)+scroll);
	if (!outside)
		delta= relOrigin-ItemRect(pp).origin;
}

void CollectionView::SetOrigin(Point at)
{
	View::SetOrigin(at);
	if (coll && coll->Size() > 0) {
		register int x, y;
		at+= gap;
		for (x= 0; x < cols; x++)
			for (y= 0; y < rows; y++)
				GetItem(x, y)->SetOrigin(Point(xPos[x]+at.x, yPos[y]+at.y));
	}
}

void CollectionView::Update()
{
	register VObject *gop;
	register int x, y, ww, hh, w, h;
	int xpos= 0, ypos= 0, sz;
	Point g;

	if (!TestFlag(eCVModified))
		return;
	ResetFlag(eCVModified);

	if (coll == 0 || coll->Size() <= 0) {
		ForceRedraw();
		SetExtent(gPoint1);
		ForceRedraw();
		return;
	}

	if (testopen) {
		{
			coll->ForEach(VObject,SetContainer)(this);
		}
		if (IsOpen()) {
			coll->ForEach(VObject,Open)(TRUE);
		}
	}

	sz= coll->Size();
	if (rows <= 0)
		SetFlag(eCVExpandRows);
	if (cols <= 0)
		SetFlag(eCVExpandCols);

	if (TestFlag(eCVExpandRows) && TestFlag(eCVExpandCols)) {
		cols= Math::Sqrt(sz);
		rows= (sz+cols-1) / cols;
	} else if (TestFlag(eCVExpandRows))
		rows= (sz+cols-1) / cols;
	else if (TestFlag(eCVExpandCols))
		cols= (sz+rows-1) / rows;

	yPos= (int*) Storage::ReAlloc(yPos, (rows+1) * sizeof(int));
	xPos= (int*) Storage::ReAlloc(xPos, (cols+1) * sizeof(int));

	xpos= ypos= 0;
	g= 2*gap;
	if (TestFlag(eCVGrid))
		g+= gPoint1;

	for (x= 0; x < cols; x++) {
		xPos[x]= xpos;
		ww= minExtent.x;
		for (y= 0; y < rows; y++) {
			gop= GetItem(x, y);
			gop->SetContainer(this);
			if (!testopen) {
				if (IsOpen())
					gop->Open();
			}
			gop->CalcExtent();
			w= gop->Width();
			ww= Math::Max(ww, w);
		}
		xpos+= ww+g.x;
	}
	xPos[x]= xpos;

	for (y= 0; y < rows; y++) {
		yPos[y]= ypos;
		hh= minExtent.y;
		for (x= 0; x < cols; x++) {
			h= GetItem(x, y)->Height();
			hh= Math::Max(hh, h);
		}
		ypos+= hh+g.y;
	}
	yPos[y]= ypos;

	for (x= 0; x < cols; x++)
		for (y= 0; y < rows; y++)
			GetItem(x, y)->SetContentRect(ItemRect(x, y).Inset(gap), FALSE);

	if (TestFlag(eCVGrid)) {
		xpos--;
		ypos--;
	}

	ForceRedraw();
	SetExtent(Point(xpos, ypos));
	ForceRedraw();
}

//---- update

void CollectionView::DoObserve(int, int part, void *, Object *op)
{
	if (part == cPartSenderDied)
		coll= 0;
	else if (op == coll)
		Modified();
}

void CollectionView::Modified()
{
	SetFlag(eCVModified);
	if (IsOpen())
		Update();
}

//---- mapping

Point CollectionView::PointToItem(const Point &pos, bool *outside)
{
	register int x, y;
	register bool out= FALSE;
	Point p(pos);

	if (coll == 0 || coll->Size() <= 0) {
		if (outside)
			*outside= TRUE;
		return gPoint0;
	}
	Update();
	p-= GetOrigin();
	if (p.x < xPos[0]) {
		out= TRUE;
		x= 0;
	} else if (p.x >= xPos[cols]) {
		out= TRUE;
		x= cols-1;
	} else {
		for (x= 0; x < cols; x++)
			if (p.x >= xPos[x] && p.x < xPos[x+1])
				break;
	}
	if (p.y < yPos[0]) {
		out= TRUE;
		y= 0;
	} else if (p.y >= yPos[rows]) {
		out= TRUE;
		y= rows-1;
	} else {
		for (y= 0; y < rows; y++)
			if (p.y >= yPos[y] && p.y < yPos[y+1])
				break;
	}
	if (outside)
		*outside= out;
	return Point(x, y);
}

Rectangle CollectionView::ItemRect(const Rectangle &r)
{

	if (r.IsEmpty())
		return gRect0;
	Update();
	Rectangle rr(xPos[r.origin.x], yPos[r.origin.y],
				xPos[r.origin.x+r.extent.x] - xPos[r.origin.x],
				yPos[r.origin.y+r.extent.y] - yPos[r.origin.y]);
	if (TestFlag(eCVGrid))
		rr.extent-= gPoint1;
	return rr+GetOrigin();
}

Point CollectionView::ItemPos(VObject *g)
{
	VObject *gop;
	Iter next(coll);

	Update();
	for (int i= 0; gop= (VObject*) next(); i++)
		if (gop == g)
			break;
	if (i >= coll->Size())
		return gPoint_1;
	return Point(i % cols, i / cols);
}

Rectangle CollectionView::ItemRect(int x, int y)
{
	Update();
	x= Math::Range(0, cols-1, x);
	y= Math::Range(0, rows-1, y);
	Rectangle r(xPos[x], yPos[y], xPos[x+1]-xPos[x], yPos[y+1]-yPos[y]);
	if (TestFlag(eCVGrid))
		r.extent-= gPoint1;
	r.origin+= GetOrigin();
	return r;
}

VObject *CollectionView::GetItem(int x, int y)
{
	VObject *vop= 0;
	int ix= y*cols+x;

	if (ix >= 0 && ix < coll->Size())
		vop= (VObject*)coll->At(ix);
	return vop ? vop : defaultItem;
}

//---- event handlung

Command *CollectionView::DoLeftButtonDownCommand(Point lp, Token t, int clicks)
{
	if (coll && coll->Size() > 0) {
		ClearSelection();
		return new CellSelector(this, TestFlag(eCVDontStuckToBorder));
	}
	return View::DoLeftButtonDownCommand(lp, t, clicks);
}

Command *CollectionView::DoKeyCommand(int ch, Token)
{
	register VObject *gop= 0;
	register int i;
	int start= 0;
	Rectangle r= GetViewedRect();

	if (r.IsNotEmpty()) {
		start= r.origin.y+r.extent.y;
		if (start <= GetExtent().y)
			start= PointToItem(Point(0,start)).y;
	}

	for (i= 0; i < rows; i++) {
		gop= GetItem(0, (start+i) % rows);
		const u_char *sort= gStdEncoding->SortMap();
		if (gop && (sort[*gop->AsString()] == sort[ch]))
			break;
	}

	if (gop)
		RevealAlign(ItemRect(Rectangle(ItemPos(gop), gPoint1)));
	return gNoChanges;
}

//---- drawing

void CollectionView::Draw(Rectangle r)
{
	register int x, y;
	register VObject *gop;
	Point p1, p2;

	if (coll == 0 || coll->Size() <= 0)
		return;
	p1= PointToItem(r.NW());
	p2= PointToItem(r.SE()+gPoint1);
	int i= 0;
	for (x= p1.x; x <= p2.x; x++) {
		for (y= p1.y; y <= p2.y; y++) {
			i++;
			gop= GetItem(x, y);
			gop->DrawAll(gop->contentRect,
				selection.ContainsPoint(Point(x, y)) && gop->Enabled());
		}
	}
	if (TestFlag(eCVGrid)) {
		GrSetPenNormal();
		DrawGrid(p1, p2);
	}
}

void CollectionView::DrawGrid(Point p1, Point p2)
{
	register int x, y;
	int xx= 0, yy= 0;
	Point o= GetOrigin();

	if (p2.x >= cols-1)
		xx= 1;
	if (p2.y >= rows-1)
		yy= 1;
	for (x= p1.x; x <= p2.x-xx; x++)
		GrLine(Point(xPos[x+1]-1, yPos[p1.y])+o, Point(xPos[x+1]-1, yPos[p2.y+1]-1)+o);
	for (y= p1.y; y <= p2.y-yy; y++)
		GrLine(Point(xPos[p1.x], yPos[y+1]-1)+o, Point(xPos[p2.x+1]-1, yPos[y+1]-1)+o);
}

//---- input/output

OStream& CollectionView::PrintOn(OStream &s)
{
	Object::PrintOn(s);
	return s << gap SP << minExtent SP << rows SP << cols SP << defaultItem SP << coll SP;
}

bool CollectionView::PrintOnWhenObserving(Object *from)
{
	return from != coll;
}

IStream& CollectionView::ReadFrom(IStream &s)
{
	SeqCollection *m;
	VObject *di= 0;

	Object::ReadFrom(s);
	s >> gap >> minExtent >> rows >> cols >> di >> m;
	SetCollection(m);
	SetDefaultItem(di);
	return s;
}

void CollectionView::CollectParts(Collection* col)
{
	View::CollectParts(col);
	if (coll && coll->Size() < 12) // hack
		col->Add(coll);
}

VObject *CollectionView::Detect(BoolFun find, void *arg)
{
	if (coll) {
		Iter next(coll);
		register VObject *v1, *v2;

		while (v1= (VObject*) next())
			if (v2= v1->Detect(find, arg))
				return v2;
	}
	return 0;
}

//---- CellSelector ------------------------------------------------------------

CellSelector::CellSelector(CollectionView* v, bool dontstucktoborder)
{
	SetFlag(eCmdNoReplFeedback);
	lvp= v;
	last= 0;
	dontstuck= dontstucktoborder;
}

void CellSelector::TrackFeedback(Point, Point pp, bool on)
{
	if (on) {
		if (itemptr == last) {
			if (last)
				last->DoTrackMouse((TrackPhase)eTrackMove, pp);
		} else {
			if (last)
				last->DoTrackMouse((TrackPhase)eTrackRelease, pp);
			if (itemptr)
				itemptr->DoTrackMouse((TrackPhase)eTrackPress, pp);
		}
		last= itemptr;
	}
}

Command *CellSelector::TrackMouse(TrackPhase atp, Point, Point, Point np)
{
	bool outside= FALSE;

	item= lvp->PointToItem(np, &outside);
	if (outside && dontstuck)
		itemptr= 0;
	else
		itemptr= lvp->GetItem(item.x, item.y);
		
	switch (atp) {
	case eTrackRelease:
		if (last)
			last->DoTrackMouse((TrackPhase)eTrackRelease, np);
		if (itemptr && itemptr->Enabled())
			lvp->SetSelection(Rectangle(item, gPoint1), TRUE);
		else
			lvp->SetSelection(gRect0, TRUE);
	case eTrackExit:
		return gNoChanges;
	default:
		break;
	}
	return this;
}

